﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

namespace Lilium.RetailAudit.Data
{
	public sealed class EntityCloner<TEntity>
	{
		static EntityCloner()
		{
			var propertyAssigners = new List<Action<TEntity, TEntity>>();
			var entityType = typeof(TEntity);

			foreach (var propertyInfo in entityType.GetProperties())
			{
				var context = new { PropertyInfo = propertyInfo };
				if (typeof(ICloneable).IsAssignableFrom(propertyInfo.PropertyType))
				{
					propertyAssigners.Add((src, dst) =>
					{
						var value = (ICloneable)context.PropertyInfo.GetValue(src, null);
						context.PropertyInfo.SetValue(dst, value != null ? value.Clone() : null, null);
					});
				}
				else
				{
					propertyAssigners.Add((src, dst) =>
					{
						var value = context.PropertyInfo.GetValue(src, null);
						context.PropertyInfo.SetValue(dst, value, null);
					});
				}
			}

			m_AssignFunc = (src, dst) =>
			{
				if (src == null) throw new ArgumentNullException("src");
				if (dst == null) throw new ArgumentNullException("dst");

				foreach (var propertyCloner in propertyAssigners)
					propertyCloner(src, dst);
			};

			m_CloneFunc = src =>
			{
				var dst = Activator.CreateInstance<TEntity>();
				AssignFunc(src, dst);
				return dst;
			};
		}

		public void Assign(TEntity src, TEntity dst)
		{
			AssignFunc(src, dst);
		}

		public TEntity Clone(TEntity src)
		{
			return CloneFunc(src);
		}

		#region public static EntityCloner<TEntity> Default

		private readonly static EntityCloner<TEntity> m_Default = new EntityCloner<TEntity>();

		public static EntityCloner<TEntity> Default
		{
			[DebuggerNonUserCodeAttribute]
			get
			{
				return m_Default;
			}
		}

		#endregion

		#region private static Func<TEntity, TEntity> CloneFunc

		private readonly static Func<TEntity, TEntity> m_CloneFunc;

		private static Func<TEntity, TEntity> CloneFunc
		{
			[DebuggerNonUserCodeAttribute]
			get
			{
				return m_CloneFunc;
			}
		}

		#endregion

		#region private static Action<TEntity, TEntity> AssignFunc

		private readonly static Action<TEntity, TEntity> m_AssignFunc;

		private static Action<TEntity, TEntity> AssignFunc
		{
			[DebuggerNonUserCodeAttribute]
			get
			{
				return m_AssignFunc;
			}
		}

		#endregion
	}
}